package com.hup.utils.swing.customView.viewTable;

import com.hup.utils.commons.ListUtil;
import com.hup.utils.commons.exception.AppCommonException;
import com.hup.utils.commons.exception.AppExceptionHandler;
import com.hup.utils.commons.exception.ErrInfo;
import com.hup.utils.commons.hardware.MouseUtil;
import com.hup.utils.commons.hardware.SystemUtil;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.swing.ComponentUtil;
import com.hup.utils.swing.KeyBoardUtil;
import com.hup.utils.swing.customView.viewTable.ColorCellRenderer.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.*;

/**
 * 简单的只读表格,支持统计行,和列排序;
 * 支持隐藏/恢复列,不支持中途增减列(不做代码限制!不能乱来)
 * <pre>
 * 对于禁用输入法
 *  可以重写 {@link #prepareEditor(TableCellEditor, int, int)}
 * </pre>
 * @author hugan
 * @date 2019/10/7
 * @apiNote JTable需要放在JScrollPane中才会显示表头
 */
@Log4j2
public class ViewTable<R extends ViewTableRowBo<R>> extends JTable implements ColorMatcher<R> {
    private static final long serialVersionUID = 4337401375383611823L;
    private static final String CMD_RESET_SORT = "重置排序";
    private static final String CMD_RESET_WIDTH = "重置列宽";
    private static final String CMD_COPY_HEAD = "复制表头";

    private ArrayList<TColumnInfo> lstColumnInfo;
    private final boolean[] columnVisibleState;
    private MyTableModel tableModel;
    /**
     * 用于实现统计行
     */
    private SummaryRowHeaderUI summaryRowHeaderUI;
    /**
     * 用于处理表格初始化或其他业务
     */
    private R sampleModel;

    @Setter
    private MyMouseListener<R> myMouseListener;
    private SummaryManager summaryManager;
    /**
     * 数据对象列表,只有在调用过 {@link #updateData(List)}后才不为空
     */
    @Getter
    private List<R> lstModelData = new ArrayList<>();
    /**
     * 表头菜单
     */
    private JPopupMenu headMenu;
    /**
     * 右键菜单
     */
    private JPopupMenu rowMenu;
    private RowMenuHandler rowMenuHandler;
    private ColorMatcher<R> rowColorMatcher;

    @Setter
    private ValueUpdateListener<R> valueUpdateListener;

    public ViewTable(R sampleModel) {
        super();
        //适配屏幕缩放后的行高;列宽目前没适配,与代码设定的像素值是一样的
        setRowHeight((int) (getRowHeight() * LookThemeUtil.getScreenScale()));
        if (sampleModel == null) {
            throw new IllegalArgumentException("sampleModel 不能为空");
        }
        this.sampleModel = sampleModel;
        //noinspection unchecked
        lstColumnInfo = (ArrayList<TColumnInfo>) sampleModel.getColumnInfo().clone();
        columnVisibleState = ListUtil.booleans(lstColumnInfo.size(), true);
        rowColorMatcher = sampleModel.getRowColorMatcher();

        getTableHeader().setReorderingAllowed(false);//不允许拖拽列
        setAutoCreateRowSorter(true);

        initView();
        initListener();
    }

    /**
     * 设置默认的渲染器,实现控制行颜色功能,重载方法;
     */
    @SuppressWarnings("unchecked")
    @Override
    protected void createDefaultRenderers() {
        defaultRenderersByColumnClass = new UIDefaults(8, 0.75f);

        // Objects
        defaultRenderersByColumnClass.put(Object.class, (UIDefaults.LazyValue) t -> new RowColorRenderer.UIResource(this));

        // Numbers
        defaultRenderersByColumnClass.put(Number.class, (UIDefaults.LazyValue) t -> new NumberRenderer(this));

        // Doubles and Floats
        defaultRenderersByColumnClass.put(Float.class, (UIDefaults.LazyValue) t -> new DoubleRenderer(this));
        defaultRenderersByColumnClass.put(Double.class, (UIDefaults.LazyValue) t -> new DoubleRenderer(this));

        // Dates
        defaultRenderersByColumnClass.put(Date.class, (UIDefaults.LazyValue) t -> new DateRenderer(this));

        // Icons and ImageIcons
        defaultRenderersByColumnClass.put(Icon.class, (UIDefaults.LazyValue) t -> new IconRenderer(this));
        defaultRenderersByColumnClass.put(ImageIcon.class, (UIDefaults.LazyValue) t -> new IconRenderer(this));

        // Booleans
        defaultRenderersByColumnClass.put(Boolean.class, (UIDefaults.LazyValue) t -> new BooleanRenderer(this));
    }

    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        try {
            return super.prepareRenderer(renderer, row, column);
        } catch (Exception e) {
            Integer colSize = ListUtil.getSize(lstColumnInfo);
            log.error("row={}, column={}, "
                            + "lstModelData.size={}, dataModel.getRowCount={}, "
                            + "columnCount={}, "
                            + "e={}",
                    row, column, lstModelData.size(), dataModel.getRowCount(),
                    colSize,
                    e.getMessage());
            Object valueAt;
            if (lstModelData.size() > row && (colSize != null && colSize > column)) {
                valueAt = getValueAt(row, column);
            } else {
                valueAt = "没有此行或列(总行数=" + ListUtil.getSize(lstModelData) + ", 列数=" + colSize + ")";
            }
            ErrInfo errInfo = AppExceptionHandler.handle("渲染错误:(" + column + "," + row + ")=" + valueAt + " :", e);
            throw new AppCommonException(errInfo.getErrMsg());
        }
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        /*
        实现"窗口偏大时,列宽自动拉伸;窗口偏小时,保持最小列宽"
        需要配合:setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
         */
        return getPreferredSize().width < getParent().getWidth();
    }

    private void initView() {
        //setAutoResizeMode(JTable.AUTO_RESIZE_OFF);//会导致,拉伸列宽时ui有bug
        setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
        String[] tableHead = TColumnInfo.getHeaderStr(lstColumnInfo);
        summaryRowHeaderUI = SummaryRowHeaderUI.initTable(this, tableHead);
        tableModel = new MyTableModel(tableHead, 0);
        setModel(tableModel);

        setColumnWidth(TColumnInfo.getWidth(lstColumnInfo), false);

        headMenu = new JPopupMenu();
        headMenu.add(new JMenuItem(CMD_RESET_SORT));
        headMenu.add(new JMenuItem(CMD_RESET_WIDTH));
        headMenu.add(new JMenuItem(CMD_COPY_HEAD));
    }

    private void initListener() {
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                ViewTable.this.mouseClicked(e);
            }
        });

        //取消选中行
        KeyBoardUtil.listen(this, (keyCode, e) -> {
            if (KeyEvent.VK_ESCAPE == keyCode) {
                clearSelection();
            }
        });

        //表头菜单功能
        ComponentUtil.addMenu(getTableHeader(), headMenu, e -> {
            switch (e.getActionCommand()) {
                case CMD_RESET_SORT:
                    getRowSorter().setSortKeys(null);
                    return;
                case CMD_RESET_WIDTH:
                    setColumnWidth(TColumnInfo.getWidth(lstColumnInfo), true);
                    return;
                case CMD_COPY_HEAD:
                    String[] tableHead = summaryRowHeaderUI.getTableHead();//获取最新的标题
                    StringBuilder sb = new StringBuilder();
                    for (String head : tableHead) sb.append(head).append('\t');
                    SystemUtil.saveToClipboard(sb.toString());
            }
        });
    }

    @Override
    public boolean isCellEditable(int row, int column) {
        Boolean res = sampleModel.isCellEditable(convertRowIndexToModel(row), convertColumnIndexToModel(column), this);
        if (res != null) {
            return res;
        }
        return false;
    }

    @Override
    public void valueChanged(ListSelectionEvent e) {
        super.valueChanged(e);
        //实现选中行时,在表格底部展示统计信息
        if (summaryManager != null) {
            summaryManager.onSelectionChanged(e);
        }
    }

    /**
     * 实现单元格更新监听
     */
    @Override
    public void setValueAt(Object newValue, int row, int column) {
        int rowIndexToModel = convertRowIndexToModel(row);
        int columnIndexToModel = convertColumnIndexToModel(column);
        TableModel model = getModel();
        Object old = model.getValueAt(rowIndexToModel, columnIndexToModel);
        if (!Objects.equals(newValue, old)) {
            model.setValueAt(newValue, rowIndexToModel, columnIndexToModel);
            log.debug("changed, old={}, newValue={}", old, newValue);
            if (valueUpdateListener != null) {
                valueUpdateListener.afterUpdate(rowIndexToModel, columnIndexToModel, newValue, old, this);
            }
        } else {
            //双击单元格后,不改变值时,除了Esc外,其他操作都会触发到达这里!需要过滤
            log.debug("changed same={}", old);
        }
    }

    @Override
    public ColorLevel getColorOf(ViewTable<R> table, int row, int column) {
        return rowColorMatcher == null ? ColorLevel.NORMAL : rowColorMatcher.getColorOf(table, row, column);
    }

    /**
     * @param resetAtNull true-如果columnWidth为空,就重置成等宽;false-如果columnWidth为空就直接返回
     */
    private void setColumnWidth(Integer[] columnWidth, boolean resetAtNull) {
        TableColumnModel columnModel = getColumnModel();
        int columnCount = columnModel.getColumnCount();
        //if (columnWidth == null) {
        //    if (!resetAtNull) {
        //        return;
        //    } else {
        //        columnWidth = new Integer[columnCount];
        //        int visibleCount = ListUtil.count(columnVisibleState, true);
        //        int width = columnModel.getTotalColumnWidth() / visibleCount;
        //        for (int i = 0; i < columnCount; i++) {
        //            columnWidth[i] = width;
        //        }
        //    }
        //}
        for (int i = 0; i < columnCount; i++) {
            if (!columnVisibleState[i]) {
                continue;//不设置隐藏行
            }
            Integer width = columnWidth[i];
            if (width != null) {
                log.debug("指定第{}列宽={}", i, width);
                columnModel.getColumn(i).setPreferredWidth(width);
            }
        }
    }

    /**
     * 获取表格当前的列宽
     */
    private static Integer[] getTableColumnPreferredWidth(JTable table) {
        TableColumnModel columnModel = table.getColumnModel();
        int columnCount = columnModel.getColumnCount();
        Integer[] widths = new Integer[columnCount];
        for (int i = 0; i < columnCount; i++) {
            widths[i] = columnModel.getColumn(i).getPreferredWidth();
        }
        return widths;
    }

    /**
     * 实现:鼠标[前后键,左键双击]监听功能
     */
    private void mouseClicked(MouseEvent e) {
        if (handleRowMenuAction(e)) {
            return;
        }
        if (myMouseListener == null) {
            return;
        }
        Boolean direction = MouseUtil.getDirection(e);
        if (direction != null) {
            myMouseListener.handleTableDirection(direction);
            return;
        }

        DefaultTableModel model = (DefaultTableModel) getModel();
        if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && model != null) {
            int selectedRow = this.convertRowIndexToModel(this.getSelectedRow());
            R row = getRow(selectedRow);
            log.debug("rowIndex={}, row={}", selectedRow, row);

            myMouseListener.onDoubleClick(row);
            return;
        }
        log.debug("其他事件");
    }

    private boolean handleRowMenuAction(MouseEvent e) {
        if (rowMenu == null) return false;
        if (!SwingUtilities.isRightMouseButton(e)) return false;

        int indexOfRow = rowAtPoint(e.getPoint());
        if (indexOfRow != -1) {
            /*
            多选菜单逻辑(根据windows的操作习惯):
            1.当前行没选中,就直接只选中当前行,并弹出菜单
            2.当前行选中,直接弹出菜单
             */
            boolean isSelected = getSelectionModel().isSelectedIndex(indexOfRow);
            if (!isSelected) {
                setRowSelectionInterval(indexOfRow, indexOfRow);
            }
            if (rowMenuHandler != null) {
                int selectedRowCount = getSelectedRowCount();
                rowMenuHandler.beforeShowMenu(selectedRowCount, rowMenu);
            }
            rowMenu.show(this, e.getX(), e.getY());
        }
        return true;
    }

    //==外部方法

    public void updateTableHead(int index, String name) {
        lstColumnInfo.get(index).setName(name);
        updateTableHead();
    }

    public R getRow(int index) {
        return lstModelData.get(index);
    }

    /**
     * 获取(第一个)选中行的数据对象
     */
    public R getSelectedRowObj() {
        //处理排序问题
        int selectedRow = convertRowIndexToModel(getSelectedRow());
        return getRow(selectedRow);
    }

    /**
     * @return 注意:(排序后)index是ui上的,不是modelList中的
     */
    @Override
    public int[] getSelectedRows() {
        return super.getSelectedRows();
    }

    /**
     * @return 排序后,index是modelList中的
     */
    public int[] getSelectedModelRows() {
        int[] selectedRows = super.getSelectedRows();
        for (int i = selectedRows.length - 1; i >= 0; i--) {
            //处理排序后index转化
            selectedRows[i] = convertRowIndexToModel(selectedRows[i]);
        }
        return selectedRows;
    }

    /**
     * 根据columnInfo更新表头
     */
    public void updateTableHead() {
        //实现保持列宽
        Integer[] columnWidth = getTableColumnPreferredWidth(this);
        summaryRowHeaderUI.updateTableHead(TColumnInfo.getHeaderStr(lstColumnInfo));
        setColumnWidth(columnWidth, false);
    }

    /**
     * 向表格追加数据
     */
    public void addData(List<R> lstData) {
        //不能直接add到lstModelData后调用updateData(lstModelData),因为updateData会先clear数据
        ArrayList<R> temp = new ArrayList<>(lstModelData.size() + lstData.size());
        temp.addAll(lstModelData);
        temp.addAll(lstData);
        updateData(temp);
    }

    /**
     * 更新表格数据,如果行对象是支持排序且重写了方法,则自动更新统计行
     * @param lstData 不会引用list对象,但会引用子item对象
     */
    public void updateData(List<R> lstData) {
        Object[] summary = sampleModel.getSummaryTableRow(lstData);
        if (summary != null) {
            //实现保持列宽
            Integer[] columnWidth = getTableColumnPreferredWidth(this);
            summaryRowHeaderUI.setSummaryData(summary);
            setColumnWidth(columnWidth, false);
        }
        if (lstData != lstModelData) {
            //fireTableDataChanged中使用的是lstModelData,则不用clear
            lstModelData.clear();
            lstModelData.addAll(lstData);
        }
        ArrayList<Vector<Object>> lstRow = new ArrayList<>(lstData.size());
        int i = 0;
        for (R data : lstData) {
            Vector<Object> vector = sampleModel.castToVector(i, data);
            lstRow.add(vector);
            i++;
        }
        updateData(lstRow);

        if (summaryManager != null) {//触发更新统计信息
            summaryManager.onSelectionNull();
        }
    }

    /**
     * 更新表格数据,不会触发更新统计行
     */
    private void updateData(ArrayList<Vector<Object>> lstRow) {
        Vector dataVector = tableModel.getDataVector();
        dataVector.clear();
        //noinspection unchecked
        dataVector.addAll(lstRow);

        tableModel.fireTableDataChanged();
    }

    /**
     * 行数据更新后,通知表格更新状态
     * @param affectCellValue true-改变的属性会影响到[单元格,表头,统计行]等;false-改变了model的属性,如只影响背景色
     */
    public void fireTableDataChanged(boolean affectCellValue) {
        if (affectCellValue) {
            //tableModel.fireTableDataChanged();//不能单纯fire,要处理统计行,行数据vector等
            //如果tableModel直接是泛型类,则可以直接fireTableDataChanged来更新表格数据,但统计行等仍然要处理
            updateData(lstModelData);
        } else {
            //单元格数据无变化时,只用重绘即可
            repaint();
        }
    }

    /**
     * 清空表格数据
     */
    public void clearData() {
        DefaultTableModel model = (DefaultTableModel) getModel();
        model.getDataVector().clear();
        lstModelData.clear();
        model.fireTableDataChanged();
        if (summaryManager != null) {
            //且情况统计行
            summaryManager.txtInfo.setText(null);
        }
    }

    /**
     * 获取选中行时,统计行信息的view
     */
    public JTextComponent getSelectionInfoComponent() {
        if (summaryManager == null) summaryManager = new SummaryManager();
        return summaryManager.txtInfo;
    }

    /**
     * @param rowMenuHandler nullable,处理展示菜单前的逻辑(如:更新菜单状态/信息等)
     */
    public void setMenu(List<JMenuItem> menuItems, RowMenuHandler rowMenuHandler) {
        rowMenu = new JPopupMenu();
        for (JMenuItem item : menuItems) {
            rowMenu.add(item);
            item.addActionListener(rowMenuHandler);
        }
        this.rowMenuHandler = rowMenuHandler;
        //鼠标右键的监听,不需要单独用ComponentUtil,本类已经有公共的鼠标监听了
    }

    ////还没测试!
    //public void setColumnVisible(boolean isVisible, int index) {
    //    columnVisibleState[index] = isVisible;
    //    TableColumn tc = getColumnModel().getColumn(index);
    //    TableColumn hc = getTableHeader().getColumnModel().getColumn(index);
    //    /*
    //    TableColumn每个setWidths后都会firePropertyChange有点坑,
    //    并且也要按照顺序set,具体看setter源码,否则会相互影响!
    //     */
    //    if (isVisible) {
    //        Integer defWidth = lstColumnInfo.get(index).getWidth();
    //        if (defWidth == null) {
    //            defWidth = 15;
    //        }
    //        /*
    //        恢复列宽:根据默认列宽源码进行恢复
    //        省缺宽度=最小值=15
    //        源码 TableColumn#TableColumn(int, int, TableCellRenderer, TableCellEditor)
    //         */
    //        tc.setMinWidth(15);
    //        tc.setPreferredWidth(defWidth);
    //        tc.setWidth(defWidth);
    //    } else {
    //        //只要这里不搞maxWidth,恢复时就不用修改
    //        tc.setMinWidth(0);
    //        tc.setWidth(0);
    //
    //        hc.setMinWidth(0);
    //        hc.setWidth(0);
    //    }
    //}

    private class MyTableModel extends DefaultTableModel {
        private static final long serialVersionUID = -6606134740013486074L;

        private MyTableModel(String[] tableHead, int rowCount) {
            super(tableHead, rowCount);
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return lstColumnInfo.get(columnIndex).getClz();
        }
    }

    /**
     * 实现选中行时,在表格底部展示统计信息
     */
    private class SummaryManager {
        private JTextArea txtInfo;
        /**
         * 用于过滤全选时的重复事件
         */
        private int lastSelectedCount;
        /**
         * 无选中时的统计信息,只有在 {@link #updateData(List)}后才更新(减少频繁的重任务)
         */
        private String noneSelectInfo;

        private SummaryManager() {
            txtInfo = new JTextArea();
            txtInfo.setEditable(false);
            txtInfo.setLineWrap(true);
            txtInfo.setBackground(new Color(238, 238, 238));
        }

        private void onSelectionChanged(ListSelectionEvent e) {
            if (getRowCount() <= 0 || getColumnCount() <= 0) {
                //过滤:调用cleanData后会触发
                return;
            }
            if (!e.getValueIsAdjusting()) {
                //getValueIsAdjusting=false时,鼠标松开,没改变内容
                //也有可能是 手工调用setRowSelectionInterval()触发的唯一一次事件,所以不能过滤
                log.debug("e={}", e);
            }
            ViewTable<R> table = ViewTable.this;
            int[] selectedRows = table.getSelectedModelRows();
            if (selectedRows.length == 0) {
                txtInfo.setText(noneSelectInfo);
                return;
            }
            if (lastSelectedCount == selectedRows.length && lastSelectedCount == table.getRowCount()) {
                //log.info("过滤全选时多余的事件");
                return;
            }
            lastSelectedCount = selectedRows.length;

            String info = sampleModel.getSelectionInfo(selectedRows, ViewTable.this);
            txtInfo.setText(info);
        }

        private void onSelectionNull() {
            noneSelectInfo = sampleModel.getSelectionInfo(new int[0], ViewTable.this);
            txtInfo.setText(noneSelectInfo);
        }
    }

    public interface MyMouseListener<R> {

        default void handleTableDirection(boolean isForward) {
        }

        default void onDoubleClick(R row) {
        }
    }

    public interface RowMenuHandler extends ActionListener {
        default void beforeShowMenu(int selectedRowCount, JPopupMenu rowMenu) {
        }

        @Override
        default void actionPerformed(ActionEvent e) {
        }
    }

    public interface ValueUpdateListener<O extends ViewTableRowBo<O>> {
        /**
         *
         * @param row 已经是model的index
         */
        void afterUpdate(int row, int col, Object newValue, Object oldValue, ViewTable<O> table);
    }

}
